#pragma once
#include "GePoint3.h"
namespace tge{

	template<typename PointT,typename RealT,typename VectorT>
	class TGeBoundBox3
	{
	public:
		PointT m_ptMin;
		PointT m_ptMax;
	public:
		TGeBoundBox3()
		{
			setEmpty();
		}
		~TGeBoundBox3()
		{
		}
		TGeBoundBox3(const TGeBoundBox3&bb)
		{
			m_ptMin = bb.m_ptMin;
			m_ptMax = bb.m_ptMax;
		}
		TGeBoundBox3(const PointT&p1, const PointT&p2)
		{
			set(p1);
			extend(p2);
		}
		TGeBoundBox3(RealT x1, RealT y1, RealT z1, RealT x2, RealT y2, RealT z2)
		{
			m_ptMin.set(x1, y1, z1);
			m_ptMax.set(x2, y2, z2);
		}
		TGeBoundBox3&setEmpty()
		{
			m_ptMin.set(1, 1, 1);
			m_ptMax.set(-1, -1, -1);
			return *this;
		}
		TGeBoundBox3& set(RealT x,RealT y,RealT z)
		{
			set(PointT(x,y,z));
			return *this;
		}
		TGeBoundBox3& extend(RealT x,RealT y,RealT z)
		{
			extend(PointT(x,y,z));
			return *this;
		}
		TGeBoundBox3& set(const RealT*v)
		{
			set(PointT(v[0],v[1],v[2]));
			return *this;
		}
		TGeBoundBox3& set(const PointT &pt)
		{
			m_ptMin = pt;
			m_ptMax = pt;
			return *this;
		}
		TGeBoundBox3&set(int vnum, const PointT*arrVts)
		{
			ASSERT(vnum > 0 && arrVts);
			set(arrVts[0]);
			for (int i = 1; i < vnum; ++i)
			{
				extend(arrVts[i]);
			}
			return *this;
		}
		TGeBoundBox3& set(const PointT &center, RealT xlen, RealT ylen, RealT zlen)
		{
			m_ptMin.set(center.x - xlen / 2, center.y - ylen / 2, center.z - zlen / 2);
			m_ptMax.set(center.x + xlen / 2, center.y + ylen / 2, center.z + zlen / 2);
			return *this;
		}
		TGeBoundBox3& extend(const TGeBoundBox3 &bb)
		{
			extend(bb.m_ptMax);
			extend(bb.m_ptMin);
			return *this;
		}
		TGeBoundBox3& extend(const PointT&pt)
		{
			ASSERT(!empty());
			if (pt.x < m_ptMin.x)m_ptMin.x = pt.x;
			else if (pt.x > m_ptMax.x)m_ptMax.x = pt.x;
			if (pt.y < m_ptMin.y)m_ptMin.y = pt.y;
			else if (pt.y > m_ptMax.y)m_ptMax.y = pt.y;
			if (pt.z < m_ptMin.z)m_ptMin.z = pt.z;
			else if (pt.z > m_ptMax.z)m_ptMax.z = pt.z;
			return *this;
		}
		bool empty()const
		{
			return m_ptMin.x > m_ptMax.x;
		}
		const PointT& maxPoint()const
		{
			return m_ptMin;
		}

		const PointT& minPoint()const
		{
			return m_ptMax;
		}
		PointT& maxPoint()
		{
			return m_ptMin;
		}

		PointT& minPoint()
		{
			return m_ptMax;
		}
		void getAllPoints(PointT pts[]) const
		{
			double x1 = m_ptMin.x;
			double y1 = m_ptMin.y;
			double z1 = m_ptMin.z;
			double x2 = m_ptMax.x;
			double y2 = m_ptMax.y;
			double z2 = m_ptMax.z;
			pts[0].set(x1, y1, z1);
			pts[1].set(x2, y1, z1);
			pts[2].set(x2, y2, z1);
			pts[3].set(x1, y2, z1);
			pts[4].set(x1, y1, z2);
			pts[5].set(x2, y1, z2);
			pts[6].set(x2, y2, z2);
			pts[7].set(x1, y2, z2);
		}
		void getQuadPoints(PointT pts[24]) const
		{
			PointT vts[8];
			getAllPoints(vts);
			int vv[] =
			{
				1, 0, 3, 2,
				0, 1, 5, 4,
				1, 2, 6, 5,
				2, 3, 7, 6,
				3, 0, 4, 7,
				4, 5, 6, 7,
			};
			for (int i = 0; i < 24; ++i)
			{
				pts[i] = vts[vv[i]];
			}
		}
		void getSize(RealT &xlen, RealT &ylen, RealT &zlen) const
		{
			xlen = fabs(m_ptMax.x - m_ptMin.x);
			ylen = fabs(m_ptMax.y - m_ptMin.y);
			zlen = fabs(m_ptMax.z - m_ptMin.z);
		}
		void getSize(RealT sz[3])const
		{
			getSize(sz[0],sz[1],sz[2]);
		}
		RealT getRadius()const
		{
			return m_ptMin.distanceTo(m_ptMax)/2;
		}
		PointT center() const
		{
			return PointT((m_ptMin.x + m_ptMax.x) / 2,
				(m_ptMin.y + m_ptMax.y) / 2,
				(m_ptMin.z + m_ptMax.z) / 2);
		}
		TGeBoundBox3& normalize()
		{
			m_ptMin.x = min(m_ptMin.x, m_ptMax.x);
			m_ptMin.y = min(m_ptMin.y, m_ptMax.y);
			m_ptMin.z = min(m_ptMin.z, m_ptMax.z);
			m_ptMax.x = max(m_ptMin.x, m_ptMax.x);
			m_ptMax.y = max(m_ptMin.y, m_ptMax.y);
			m_ptMax.z = max(m_ptMin.z, m_ptMax.z);
			return *this;
		}
		void offset_point(PointT&pt,RealT dx,RealT dy,RealT dz)const
		{
			pt.x += dx;
			pt.y += dy;
			pt.z += dz;
		}
		TGeBoundBox3& offset(RealT dx,RealT dy,RealT dz)
		{
			offset_point(m_ptMin, dx, dy, dz);
			offset_point(m_ptMax, dx, dy, dz);
			return *this;
		}
		bool isEqual(const TGeBoundBox3&bb, double tol=1.0e-7) const
		{
			return m_ptMin.isEqual(bb.m_ptMin, tol) && m_ptMax.isEqual(bb.m_ptMax, tol);
		}
		RealT volume() const
		{
			RealT xx, yy, zz;
			getSize(xx, yy, zz);
			return xx*yy*zz;
		}
		RealT radius() const
		{
			return m_ptMin.distanceTo(m_ptMax) / 2.0;
		}
		TGeBoundBox3& swell(double dist)
		{
			m_ptMin.x -= dist;
			m_ptMin.y -= dist;
			m_ptMin.z -= dist;
			m_ptMax.x += dist;
			m_ptMax.y += dist;
			m_ptMax.z += dist;
			return *this;
		}
		TGeBoundBox3& swell(double x, double y, double z)
		{
			m_ptMin.x -= x;
			m_ptMin.y -= y;
			m_ptMin.z -= z;
			m_ptMax.x += x;
			m_ptMax.y += y;
			m_ptMax.z += z;
			return *this;
		}
		bool contains(const PointT &pt)const
		{
			if (pt.x<m_ptMin.x || pt.x>m_ptMax.x)return false;
			if (pt.y<m_ptMin.y || pt.y>m_ptMax.y)return false;
			if (pt.z<m_ptMin.z || pt.z>m_ptMax.z)return false;
			return TRUE;
		}
		bool contains(const TGeBoundBox3&bbox)const
		{
			return contains(bbox.minPoint()) && contains(bbox.maxPoint());
		}
		bool disjoint(const TGeBoundBox3 &bb, RealT tol=1.0e-7) const
		{
			if (bb.m_ptMax.x<m_ptMin.x - tol || bb.m_ptMin.x>m_ptMax.x + tol)return true;
			if (bb.m_ptMax.y<m_ptMin.y - tol || bb.m_ptMin.y>m_ptMax.y + tol)return true;
			if (bb.m_ptMax.z<m_ptMin.z - tol || bb.m_ptMin.z>m_ptMax.z + tol)return true;
			return false;
		}
		bool intersectWithRay(
			const PointT&p0
			, const VectorT&dir
			, PointT&ptHit)const
		{
			if (this->contains(p0))
			{
				return TRUE;
			}
			GePoint3d p2 = p0 + dir * 2000;
			const double tol = 1.0e-20;
			double tmin = -1e20, tmax = 1e20;
			double t, f, t1, t2;
			const GeVector3d&v = dir;
			for (int i = 0; i < 3; ++i)
			{
				if (fabs(v[i]) > tol)
				{
					f = 1 / v[i];
					t2 = (m_ptMax[i] - p0[i])*f;
					t1 = (m_ptMin[i] - p0[i])*f;
					if (t1 > t2) std::swap(t1, t2);
					if (t1 > tmin) tmin = t1;
					if (t2 < tmax) tmax = t2;
					if (tmin > tmax || tmax < 0)
					{
						t = 0;
						return false;
					}
				}
				else if (p0[i] > m_ptMax[i] || p0[i] < m_ptMin[i])
				{
					t = 0;
					return false;
				}
			}
			if (tmin > 0)t = tmin;
			else
			{
				t = 0;
				return false;
			}
			ptHit = p0 + t*dir;
			return true;
		}
	};

	typedef TGeBoundBox3<GePoint3d,double,GeVector3d> GeBoundBox3d;
}